using System;
using System.IO;


namespace GameFrame.Deterministic
{
    /// <summary>
    /// FP lookup table. Used internally by trigonometric and square root functions.
    /// </summary>
    /// 
    ///             \ingroup MathAPI
    public static class FPLut
    {
        public const int PRECISION = 16;
        public const long PI = 205887;
        public const long PITIMES2 = 411774;
        public const long PIOVER2 = 102944;
        public const long ONE = 65536;
        internal const int SQRT_RESOLUTION_SPACE = 3;
        internal const int SQRT_LUT_SIZE_BASE_2 = 16;
        internal const int SQRT_VALUE_STEP = 3;
        internal const int SQRT_LUT_SIZE_BASE_10 = 65536;
        internal const int SqrtAdditionalPrecisionBits = 6;
        internal const int Log2LutSizeExponent = 6;
        internal const int Log2AdditionalPrecisionBits = 15;
        /// <summary>
        /// How much Log2 additional precision (AP) result needs to be shifted to allow for a safe FPHighPrecision division.
        /// There must be a way to calculate this, but I have a brain fog atm.
        /// 6 is the safe choice here - max log2 is 48.
        /// </summary>
        internal const int Log2APShiftForHPDivision = 6;
        internal const int ExpNegativeLutPrecision = 42;
        internal const int ExpNegativeLutCount = 30;
        internal const int ExpNonNegativeLutCount = 33;
        internal const int ExpOverflowingThreshold = 20;
        public static int[] sqrt_aprox_lut;
        public static long[] asin_lut;
        public static long[] acos_lut;
        public static long[] atan_lut;
        public static long[] sin_cos_lut;
        public static long[] tan_lut;
        public static uint[] log2_approx_lut;
        public static long[] exp_integral_lut;

        public static bool IsLoaded => FPLut.sin_cos_lut != null && FPLut.sin_cos_lut.Length != 0 && FPLut.tan_lut != null && FPLut.tan_lut.Length != 0 && FPLut.asin_lut != null && FPLut.asin_lut.Length != 0 && FPLut.acos_lut != null && FPLut.acos_lut.Length != 0 && FPLut.atan_lut != null && FPLut.atan_lut.Length != 0 && FPLut.sqrt_aprox_lut != null && FPLut.sqrt_aprox_lut.Length != 0;

        /// <summary>
        /// Initialize LUT from directory <paramref name="directoryPath" />. The directory needs to have following files:
        /// * FPSin.bytes
        /// * FPCos.bytes
        /// * FPTan.bytes
        /// * FPAsin.bytes
        /// * FPAcos.bytes
        /// * FPAtan.bytes
        /// * FPSqrt.bytes
        /// </summary>
        /// <param name="directoryPath"></param>
        public static void Init(string directoryPath)
        {
            FPLut.Load<long>(directoryPath, "FPSinCos", ref FPLut.sin_cos_lut);
            FPLut.Load<long>(directoryPath, "FPTan", ref FPLut.tan_lut);
            FPLut.Load<long>(directoryPath, "FPAsin", ref FPLut.asin_lut);
            FPLut.Load<long>(directoryPath, "FPAcos", ref FPLut.acos_lut);
            FPLut.Load<long>(directoryPath, "FPAtan", ref FPLut.atan_lut);
            FPLut.Load<int>(directoryPath, "FPSqrt", ref FPLut.sqrt_aprox_lut);
            FPLut.InitSmallLut();
        }

        /// <summary>
        /// Initialize LUT using <paramref name="lutProvider" />. The provider needs to be able to load following paths:
        /// * FPSin
        /// * FPCos
        /// * FPTan
        /// * FPAsin
        /// * FPAcos
        /// * FPAtan
        /// * FPSqrt
        /// </summary>
        /// <param name="lutProvider"></param>
        public static void Init(LutProvider lutProvider)
        {
            FPLut.Load<long>(lutProvider, "FPSinCos", ref FPLut.sin_cos_lut);
            FPLut.Load<long>(lutProvider, "FPTan", ref FPLut.tan_lut);
            FPLut.Load<long>(lutProvider, "FPAsin", ref FPLut.asin_lut);
            FPLut.Load<long>(lutProvider, "FPAcos", ref FPLut.acos_lut);
            FPLut.Load<long>(lutProvider, "FPAtan", ref FPLut.atan_lut);
            FPLut.Load<int>(lutProvider, "FPSqrt", ref FPLut.sqrt_aprox_lut);
            FPLut.InitSmallLut();
        }

        private static void InitSmallLut()
        {
            FPLut.log2_approx_lut = new uint[66]
            {
                0U,
                48034513U,
                95335645U,
                141925456U,
                187825021U,
                233054496U,
                277633165U,
                321579490U,
                364911162U,
                407645136U,
                449797678U,
                491384396U,
                532420281U,
                572919734U,
                612896598U,
                652364189U,
                691335320U,
                729822324U,
                767837083U,
                805391046U,
                842495250U,
                879160341U,
                915396590U,
                951213914U,
                986621888U,
                1021629764U,
                1056246482U,
                1090480686U,
                1124340739U,
                1157834731U,
                1190970490U,
                1223755601U,
                1256197405U,
                1288303019U,
                1320079339U,
                1351533050U,
                1382670639U,
                1413498396U,
                1444022426U,
                1474248656U,
                1504182841U,
                1533830570U,
                1563197273U,
                1592288229U,
                1621108567U,
                1649663276U,
                1677957208U,
                1705995083U,
                1733781493U,
                1761320910U,
                1788617686U,
                1815676059U,
                1842500157U,
                1869094003U,
                1895461516U,
                1921606515U,
                1947532725U,
                1973243777U,
                1998743213U,
                2024034488U,
                2049120974U,
                2074005959U,
                2098692655U,
                2123184198U,
                2147483648U,
                2171593995U
            };
            FPLut.exp_integral_lut = new long[63]
            {
                0L,
                1L,
                3L,
                8L,
                22L,
                61L,
                166L,
                451L,
                1226L,
                3334L,
                9065L,
                24641L,
                66982L,
                182076L,
                494934L,
                1345372L,
                3657101L,
                9941033L,
                27022531L,
                73454856L,
                199671002L,
                542762058L,
                1475380240L,
                4010499297L,
                10901667362L,
                29633804291L,
                80553031713L,
                218965842333L,
                595210870268L,
                1617950892750L,
                65536L,
                178145L,
                484249L,
                1316325L,
                3578144L,
                9726404L,
                26439109L,
                71868950L,
                195360062L,
                531043708L,
                1443526462L,
                3923911751L,
                10666298010L,
                28994004058L,
                78813874367L,
                214238322522L,
                582360139072L,
                1583018983658L,
                4303091737384L,
                11697016075923L,
                31795786246376L,
                1318815734L,
                3584912846L,
                9744803446L,
                26489122130L,
                72004899337L,
                195729609429L,
                532048240602L,
                1446257064291L,
                3931334297144L,
                10686474581524L,
                29048849665247L,
                78962960182681L
            };
        }

        /// <summary>
        /// Generate lookup tables in <paramref name="directoryPath" />.
        /// </summary>
        /// <param name="directoryPath"></param>
        public static void GenerateTables(string directoryPath) => LutGenerator.Generate(directoryPath);

        private static void Load<T>(LutProvider lutProvider, string path, ref T[] lut) where T : unmanaged
        {
            byte[] src = lutProvider(path);
            lut = new T[src.Length / sizeof (T)];
            Buffer.BlockCopy((Array) src, 0, (Array) lut, 0, src.Length);
        }

        private static void Load<T>(string directoryPath, string filePath, ref T[] lut) where T : unmanaged
        {
            byte[] src = File.ReadAllBytes(Path.Combine(directoryPath, filePath) + ".bytes");
            lut = new T[src.Length / sizeof (T)];
            Buffer.BlockCopy((Array) src, 0, (Array) lut, 0, src.Length);
        }
    }

    public delegate byte[] LutProvider(string path);
}